﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;

/*=====================================================================
  This file is part of a Microsoft SQL Server Shared Source Application.
  Copyright (C) Microsoft Corporation.  All rights reserved.
 
THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.
======================================================= */


namespace Microsoft.SqlCommunity.FaultRetry
{
    /// <summary>
    /// Part of the implementation of a custom .NET Framework Data Provider which wraps the SQL Server data provider.
    /// This custom data provider  retries operations which 
    /// fail to enable automatic failover to a mirror server.  

    /// Encapsulates a SqlDataAdapter with retry logic which allows seemless automatic client failover 
    /// from principal to mirror server.  Data adapters are the bridge between data tables/data sets and the actual back end.
    /// </summary>
    public class FaultRetryDataAdapter : IDbDataAdapter
    {

        protected SqlDataAdapter _adapter = new SqlDataAdapter();  //The underlying data adapter
        protected FaultRetryCommand _selectCommand = null;  //How to query for data
        protected FaultRetryCommand _insertCommand = null;  //How to create a row of data
        protected FaultRetryCommand _updateCommand = null;  //How to alter a row of data
        protected FaultRetryCommand _deleteCommand = null;  //How to remove a row of data
        private FaultRetryConnection _connection = null;    //The path to a server and database for invoking commands

        /// <summary>
        /// The path to a server and database for invoking commands
        /// </summary>
        public FaultRetryConnection Connection
        {
          get { return _connection; }
          set 
          { 
              _connection = value;
          }
        }

        //
        // Inherit from Component through DbDataAdapter. The event
        // mechanism is designed to work with the Component.Events
        // property. These variables are the keys used to find the
        // events in the components list of events.
        //
        static private readonly object EventRowUpdated = new object();
        static private readonly object EventRowUpdating = new object();

        
        /// <summary>
        /// The constructor used to instantiate the data adapter when the select command and connection
        /// will be specified later.
        /// </summary>
        public FaultRetryDataAdapter()
        {
        }

        /// <summary>
        /// The constructor used to instantiate the data adapter and initialize the query command
        /// </summary>
        /// <param name="selectCommand">The command used to query for data</param>
        public FaultRetryDataAdapter(FaultRetryCommand selectCommand)
        {
            _selectCommand = selectCommand;
            this.Connection = selectCommand.Connection as FaultRetryConnection;
            ValidateConnection();
        }


        /// <summary>
        /// The constructor used to instantiate the data adapter and initialize the query command
        /// from supplied text, and initialize the connection with the connection string text.
        /// </summary>
        /// <param name="selectCommandText">How to query the database</param>
        /// <param name="selectConnectionString">The path to the server and database to access</param>
        public FaultRetryDataAdapter(string selectCommandText, string selectConnectionString)
        {
            this.Connection = new FaultRetryConnection(selectConnectionString);
            _selectCommand = new FaultRetryCommand(selectCommandText, _connection);
        }

        /// <summary>
        /// Similar to above but supplying the connection object rather than the connection string.
        /// </summary>
        /// <param name="selectCommandText">How to query the database</param>
        /// <param name="connection">The object which represents the path to the server and database to access</param>
        public FaultRetryDataAdapter(string selectCommandText, FaultRetryConnection connection)
        {
            this.Connection = connection;
            _selectCommand = (FaultRetryCommand)connection.CreateCommand();
            _selectCommand.CommandText = selectCommandText;
        }

        /// <summary>
        /// How to query the data
        /// </summary>
        public FaultRetryCommand SelectCommand
        {
            get { return _selectCommand; }
            set 
            {
                _selectCommand = value;
                _adapter.SelectCommand = value.InnerCommand;
                if (_connection == null)
                {
                    _connection = (FaultRetryConnection)_selectCommand.Connection;
                }
            }
        }

        /// <summary>
        /// How to query the data
        /// </summary>
        IDbCommand IDbDataAdapter.SelectCommand
        {
            get { return _selectCommand; }
            set 
            { 
                _selectCommand = (FaultRetryCommand)value;
                _adapter.SelectCommand = _selectCommand.InnerCommand;
            }
        }

        /// <summary>
        /// How to add a new row
        /// </summary>
        public FaultRetryCommand InsertCommand
        {
            get { return _insertCommand; }
            set 
            { 
                _insertCommand = value;
                _adapter.InsertCommand = value.InnerCommand;
            }
        }

        /// <summary>
        /// How to add a new row
        /// </summary>
        IDbCommand IDbDataAdapter.InsertCommand
        {
            get { return _insertCommand; }
            set 
            { 
                _insertCommand = (FaultRetryCommand)value;
                _adapter.InsertCommand = _insertCommand.InnerCommand;
            }
        }

        /// <summary>
        /// How to update a row
        /// </summary>
        public FaultRetryCommand UpdateCommand
        {
            get { return _updateCommand; }
            set 
            { 
                _updateCommand = value;
                _adapter.UpdateCommand = value.InnerCommand;
            }
        }

        /// <summary>
        /// How to update a row
        /// </summary>
        IDbCommand IDbDataAdapter.UpdateCommand
        {
            get { return _updateCommand; }
            set 
            { 
                _updateCommand = (FaultRetryCommand)value;
                _adapter.UpdateCommand = _updateCommand.InnerCommand;
            }
        }

        /// <summary>
        /// How to remove a row
        /// </summary>
        public FaultRetryCommand DeleteCommand
        {
            get { return _deleteCommand; }
            set 
            { 
                _deleteCommand = value;
                _adapter.DeleteCommand = value.InnerCommand;
            }
        }

        /// <summary>
        /// How to remove a row
        /// </summary>
        IDbCommand IDbDataAdapter.DeleteCommand
        {
            get { return _deleteCommand; }
            set 
            { 
                _deleteCommand = (FaultRetryCommand)value;
                _adapter.DeleteCommand = _deleteCommand.InnerCommand;
            }
        }

        /// <summary>
        /// The event raised before a row is updated
        /// </summary>
        public event SqlRowUpdatingEventHandler RowUpdating
        {
            add { _adapter.RowUpdating += value; }                
            remove { _adapter.RowUpdating -= value; }
        }

        /// <summary>
        /// The event raise after a row is updated
        /// </summary>
        public event SqlRowUpdatedEventHandler RowUpdated
        {
            add { _adapter.RowUpdated += value; }
            remove { _adapter.RowUpdated -= value;  }
        }

        /// <summary>
        /// The master mapping between a source table and a data table
        /// </summary>
        public ITableMappingCollection TableMappings
        {
            get { return _adapter.TableMappings; }
        }

        /// <summary>
        /// What to do when the data from the database doesn't match the data set schema
        /// </summary>
        public MissingSchemaAction MissingSchemaAction
        {
            get { return _adapter.MissingSchemaAction; }
            set { _adapter.MissingSchemaAction = value; }
        }

        /// <summary>
        /// What to do when the data from the database doesn't match the data set schema
        /// </summary>
        public MissingMappingAction MissingMappingAction
        {
            get { return _adapter.MissingMappingAction; }
            set { _adapter.MissingMappingAction = value; }
        }

        /// <summary>
        /// Determine the changes and persist them to the database.
        /// </summary>
        /// <param name="data">A dataset with changes to persist</param>
        /// <returns>Rows affected</returns>
        public int Update(DataSet data)
        {
            ValidateConnection();
            return _connection.WithMirrorRetry<int>(() => _adapter.Update(data));
        }

        /// <summary>
        /// Determine the changes and persist them to the database.
        /// </summary>
        /// <param name="dataRows">A set of rows to consider</param>
        /// <returns>Rows affected</returns>
        public int Update(DataRow[] dataRows)
        {
            return _connection.WithMirrorRetry<int>(() => _adapter.Update(dataRows));
        }

        /// <summary>
        /// Determine the changes and persist them to the database.
        /// </summary>
        /// <param name="table">A set of rows to consider</param>
        /// <returns>Rows affected</returns>
        public int Update(DataTable table)
        {
            return _connection.WithMirrorRetry<int>(() => _adapter.Update(table));
        }

        /// <summary>
        /// Determine the changes and persist them to the database.
        /// </summary>
        /// <param name="data">A data set containing a specific table to consider</param>
        /// <param name="tableName">The name of the specific table</param>
        /// <returns>Rows affected</returns>
        public int Update(DataSet data, string tableName)
        {
            return _connection.WithMirrorRetry<int>(() => _adapter.Update(data, tableName));
        }

        /// <summary>
        /// The parameters to use when executing the SELECT command
        /// </summary>
        /// <returns></returns>
        public IDataParameter[] GetFillParameters()
        {
            return _adapter.GetFillParameters();
        }

        /// <summary>
        /// Fill a data set with data from the database based on the SELECT command specified when setting up the adapter.
        /// </summary>
        /// <param name="data">The data set to fill</param>
        /// <returns>Number of rows</returns>
        public int Fill(DataSet data)
        {
            return _connection.WithMirrorRetry<int>(() => _adapter.Fill(data));
        }

        /// <summary>
        /// Fill a data table with data from the database based on the SELECT command specified when setting up the adapter.
        /// </summary>
        /// <param name="table">The data table to fill</param>
        /// <returns>Number of rows</returns>
        public int Fill(DataTable table)
        {
            return _connection.WithMirrorRetry<int>(() => _adapter.Fill(table));
        }

        /// <summary>
        /// Fill a data table with data from the database based on the SELECT command specified when setting up the adapter.
        /// </summary>
        /// <param name="data">The data set where the data table resides</param>
        /// <param name="tableName">The name of the data table</param>
        /// <returns></returns>
        public int Fill(DataSet data, string tableName)
        {
            return _connection.WithMirrorRetry<int>(() => _adapter.Fill(data, tableName));
        }

        /// <summary>
        /// Fill a data set with schema information from the database
        /// </summary>
        /// <param name="data">The data set to fill</param>
        /// <param name="type">How to handle existing schema mappings</param>
        /// <returns>The data tables configured with the schema information</returns>
        public DataTable[] FillSchema(DataSet data, SchemaType type)
        {
            return _connection.WithMirrorRetry<DataTable[]>(() => _adapter.FillSchema(data, type));
        }

        /// <summary>
        /// Similar to above but operates on a single table
        /// </summary>
        /// <param name="table">The data table to fill</param>
        /// <param name="type">How to handle existing schema mappings</param>
        /// <returns>The data table with schema information.</returns>
        public DataTable FillSchema(DataTable table, SchemaType type)
        {
            return _connection.WithMirrorRetry<DataTable>(() => _adapter.FillSchema(table, type));
        }

        /// <summary>
        /// Similar to above but operates on a table in a data set.
        /// </summary>
        /// <param name="data">The data set to add the data table to</param>
        /// <param name="type"></param>
        /// <param name="tableName">The name of the table</param>
        public DataTable[] FillSchema(DataSet data, SchemaType type, string tableName)
        {
            return _connection.WithMirrorRetry<DataTable[]>(() => _adapter.FillSchema(data, type, tableName));
        }

        /// <summary>
        /// Controls whether AcceptChanges is invoked when the Update method is invoked.
        /// </summary>
        public bool AcceptChangesDuringUpdate
        {
            get { return _adapter.AcceptChangesDuringUpdate; }
            set { _adapter.AcceptChangesDuringUpdate = value; }
        }

        /// <summary>
        /// Controls whether AcceptChanges is invoked when the Fill method is invoked.
        /// </summary>
        public bool AcceptChangesDuringFill
        {
            get { return _adapter.AcceptChangesDuringFill; }
            set { _adapter.AcceptChangesDuringFill = value; }
        }

        /// <summary>
        /// Ensure that the adapter is correctly configured.
        /// </summary>
        private void ValidateConnection()
        {
            if (_connection == null) 
                throw new ArgumentException("Connection for MirrorDataAdapter must be a non-null MirrorConnection.");
        }


    }



}
